#include <stdio.h>
#include <string.h>
#include "trng.h"


//keep current choice
static uint8_t use_post_processing = 1;       //1:use, 0:not use



void trng_set_post_processing(uint8_t config)
{
	use_post_processing = config;
}


void trng_with_post_processing()
{
	TRNG_RTCR |= 1;
}


void trng_without_post_processing()
{
	TRNG_RTCR &= (~1);
}


void trng_enable()
{
	TRNG_CR |= 1;
}


void trng_disable()
{
	TRNG_CR &= (~1);
}


// make sure enable_config < 16
void ro_enable(uint8_t enable_config)
{

	TRNG_CR &= (0x0F<<1);
	TRNG_CR |= ((enable_config & 0x0F)<<1);
}

// make sure ro_series_number < 4
void ro_internal_enable(uint8_t ro_series_number, uint16_t value)
{
	switch(ro_series_number)
	{
	case 0:
		TRNG_RO_CR &= 0x0000FFFF;
		TRNG_RO_CR |= (value<<16);
		break;

	case 1:
		TRNG_RO_CR &= 0xFFFF0000;
		TRNG_RO_CR |= value;
		break;

	case 2:
		TRNG_RO_CR2 &= 0x0000FFFF;
		TRNG_RO_CR2 |= (value<<16);
		break;

	case 3:
		TRNG_RO_CR2 &= 0xFFFF0000;
		TRNG_RO_CR2 |= value;
		break;

	default:
		break;
	}
}


void trng_set_freq(uint8_t freq)
{
	TRNG_RO_CR3 = freq;
}


void trng_set_fifo_depth(uint8_t FIFO_depth)
{
	if(use_post_processing)
	{
		TRNG_FIFO_CR &= (~((0x7F)<<16));
		TRNG_FIFO_CR |= (FIFO_depth<<16);
	}
	else
	{
		TRNG_FIFO_CR &= (~(0x7F));
		TRNG_FIFO_CR |= FIFO_depth;
	}
}


/* function: wait till rand FIFO ready
 * parameters: none
 * return: count of words in FIFO
 * caution:
 */
uint8_t trng_wait_till_ready()
{
	uint32_t empty_flag;
	volatile uint8_t count;

	if(use_post_processing)
	{
		empty_flag = (1<<8);
	}
	else
	{
		empty_flag = (1<<24);
	}

WAIT_TILL_READY:

	//wait till done
	while(TRNG_FIFO_SR & empty_flag)
	{
		//HT test fail
		if(TRNG_SR & 1)
		{
			trng_disable();

			TRNG_SR = 7;   //clear flag

			//wait for a while
			count = 0x3F;
			while(count)
			{
				count--;
			}

			trng_enable();

			//wait for a while
			count = 0x3F;
			while(count)
			{
				count--;
			}

			goto WAIT_TILL_READY;
		}
	}

	//return counts of random in FIFO
	if(use_post_processing)
	{
		return (TRNG_FIFO_SR & 0xFF);
	}
	else
	{
		return ((TRNG_FIFO_SR>>16) & 0xFF);
	}
}



//trng config flag
int trng_cfg_flag=0;

/* function: get rand
 * parameters:
 *     a -------------------------- input, byte buffer a
 *     byteLen -------------------- input, byte length of rand
 * return: 0(success), other(error)
 * caution:
 */
uint8_t get_rand(uint8_t *a, uint32_t byteLen)
{
	uint32_t i;
	uint32_t tmp, rng_data;
	uint8_t count;

	if(NULL == a)
	{
		return TRNG_BUFFER_NULL;
	}

	if(0 == byteLen)
	{
		return TRNG_SUCCESS;
	}

	if(0 == trng_cfg_flag)
	{
		trng_cfg_flag = 1;

		trng_disable();

		TRNG_SR = 7;   //clear flag

		if(1 == use_post_processing)
		{
			trng_with_post_processing();
		}
		else
		{
			trng_without_post_processing();
		}

		//default config
	#if 0
		ro_enable(0x0F);
		ro_internal_enable(0, 0xFFFF);
		ro_internal_enable(1, 0xFFFF);
		ro_internal_enable(2, 0xFFFF);
		ro_internal_enable(3, 0xFFFF);

		trng_set_freq(TRNG_RO_FREQ_32);
		trng_set_fifo_depth(DEFAULT_FIFO_DEPTH);
	#endif

		trng_enable();
	}

	tmp = ((uint64_t)a) & 3;
	if(tmp)
	{
		i = 4-tmp;

		trng_wait_till_ready();
		rng_data = TRNG_DR;
		if(byteLen > i)
		{
			memcpy(a, &rng_data, i);
			a += i;
			byteLen -= i;
		}
		else
		{
			memcpy(a, &rng_data, byteLen);
			return TRNG_SUCCESS;
		}
	}

	tmp = byteLen/4;
	while(tmp)
	{
		count = trng_wait_till_ready();
		if(count > tmp)
		{
			count = tmp;
		}

		for(i=0; i<count; i++)
		{
			*((uint32_t *)a) = TRNG_DR;   //printf("\r\n rand = %08x ", *((uint32_t *)a));
			a += 4;
		}

		tmp -= count;
	}

	byteLen = byteLen & 3;
	if(byteLen)
	{
		trng_wait_till_ready();
		rng_data = TRNG_DR;
		memcpy(a, &rng_data, byteLen);
	}

	return TRNG_SUCCESS;
}

